// ======================================================================
// \title  BufferLoggerFile.cpp
// \author bocchino, dinkel, mereweth
// \brief  Implementation for Svc::BufferLogger::BufferLoggerFile
//
// \copyright
// Copyright (C) 2015-2017 California Institute of Technology.
// ALL RIGHTS RESERVED.  United States Government Sponsorship
// acknowledged.
//
// ======================================================================

#include "Os/ValidateFile.hpp"
#include "Os/ValidatedFile.hpp"
#include "Svc/BufferLogger/BufferLogger.hpp"

namespace Svc {

// ----------------------------------------------------------------------
// Constructors and destructors
// ----------------------------------------------------------------------

BufferLogger::File ::File(BufferLogger& bufferLogger)
    : m_bufferLogger(bufferLogger),
      m_prefix(""),
      m_suffix(""),
      m_baseName(""),
      m_fileCounter(0),
      m_maxSize(0),
      m_sizeOfSize(0),
      m_mode(Mode::CLOSED),
      m_bytesWritten(0) {}

BufferLogger::File ::~File() {
    this->close();
}

// ----------------------------------------------------------------------
// Public functions
// ----------------------------------------------------------------------

void BufferLogger::File ::init(const char* const logFilePrefix,
                               const char* const logFileSuffix,
                               const FwSizeType maxFileSize,
                               const U8 sizeOfSize) {
    // NOTE(mereweth) - only call this before opening the file
    FW_ASSERT(this->m_mode == File::Mode::CLOSED);

    this->m_prefix = logFilePrefix;
    this->m_suffix = logFileSuffix;
    this->m_maxSize = maxFileSize;
    this->m_sizeOfSize = sizeOfSize;

    FW_ASSERT(sizeOfSize <= sizeof(FwSizeType), static_cast<FwAssertArgType>(sizeOfSize));
    FW_ASSERT(m_maxSize > sizeOfSize, static_cast<FwAssertArgType>(m_maxSize));
}

void BufferLogger::File ::setBaseName(const Fw::ConstStringBase& baseName) {
    if (this->m_mode == File::Mode::OPEN) {
        this->closeAndEmitEvent();
    }
    this->m_baseName = baseName;
    this->m_fileCounter = 0;
    this->open();
}

void BufferLogger::File ::logBuffer(const U8* const data, const FwSizeType size) {
    // Close the file if it will be too big
    if (this->m_mode == File::Mode::OPEN) {
        const FwSizeType projectedByteCount = this->m_bytesWritten + this->m_sizeOfSize + size;
        if (projectedByteCount > this->m_maxSize) {
            this->closeAndEmitEvent();
        }
    }
    // Open a file if necessary
    if (this->m_mode == File::Mode::CLOSED) {
        this->open();
    }
    // Write to the file if it is open
    if (this->m_mode == File::Mode::OPEN) {
        (void)this->writeBuffer(data, size);
    }
}

void BufferLogger::File ::closeAndEmitEvent() {
    if (this->m_mode == File::Mode::OPEN) {
        this->close();
        Fw::LogStringArg logStringArg(this->m_name.toChar());
        this->m_bufferLogger.log_DIAGNOSTIC_BL_LogFileClosed(logStringArg);
    }
}

// ----------------------------------------------------------------------
// Private functions
// ----------------------------------------------------------------------

void BufferLogger::File ::open() {
    FW_ASSERT(this->m_mode == File::Mode::CLOSED);

    // NOTE(mereweth) - check that file path has been set and that initLog has been called
    if ((this->m_baseName.toChar()[0] == '\0') || (this->m_sizeOfSize > sizeof(FwSizeType)) ||
        (this->m_maxSize <= this->m_sizeOfSize)) {
        this->m_bufferLogger.log_WARNING_HI_BL_NoLogFileOpenInitError();
        return;
    }

    if (this->m_fileCounter == 0) {
        this->m_name.format("%s%s%s", this->m_prefix.toChar(), this->m_baseName.toChar(), this->m_suffix.toChar());
    } else {
        this->m_name.format("%s%s%" PRI_FwSizeType "%s", this->m_prefix.toChar(), this->m_baseName.toChar(),
                            this->m_fileCounter, this->m_suffix.toChar());
    }

    const Os::File::Status status = this->m_osFile.open(this->m_name.toChar(), Os::File::OPEN_WRITE);
    if (status == Os::File::OP_OK) {
        this->m_fileCounter++;
        // Reset bytes written
        this->m_bytesWritten = 0;
        // Set mode
        this->m_mode = File::Mode::OPEN;
    } else {
        Fw::LogStringArg string(this->m_name.toChar());
        this->m_bufferLogger.log_WARNING_HI_BL_LogFileOpenError(status, string);
    }
}

bool BufferLogger::File ::writeBuffer(const U8* const data, const FwSizeType size) {
    bool status = this->writeSize(size);
    if (status) {
        status = this->writeBytes(data, size);
    }
    return status;
}

bool BufferLogger::File ::writeSize(const FwSizeType size) {
    FW_ASSERT(this->m_sizeOfSize <= sizeof(FwSizeType));
    U8 sizeBuffer[sizeof(FwSizeType)];
    FwSizeType sizeRegister = size;
    for (U8 i = 0; i < this->m_sizeOfSize; ++i) {
        sizeBuffer[this->m_sizeOfSize - i - 1] = sizeRegister & 0xFF;
        sizeRegister >>= 8;
    }
    const bool status = this->writeBytes(sizeBuffer, this->m_sizeOfSize);
    return status;
}

bool BufferLogger::File ::writeBytes(const void* const data, const FwSizeType length) {
    FwSizeType size = length;
    const Os::File::Status fileStatus = this->m_osFile.write(reinterpret_cast<const U8*>(data), size);
    bool status;
    if (fileStatus == Os::File::OP_OK && static_cast<FwSizeType>(size) == length) {
        this->m_bytesWritten += length;
        status = true;
    } else {
        Fw::LogStringArg string(this->m_name.toChar());

        this->m_bufferLogger.log_WARNING_HI_BL_LogFileWriteError(fileStatus, static_cast<U32>(size),
                                                                 static_cast<U32>(length), string);
        status = false;
    }
    return status;
}

void BufferLogger::File ::writeHashFile() {
    Os::ValidatedFile validatedFile(this->m_name.toChar());
    const Os::ValidateFile::Status status = validatedFile.createHashFile();
    if (status != Os::ValidateFile::VALIDATION_OK) {
        const Fw::ConstStringBase& hashFileName = validatedFile.getHashFileName();
        Fw::LogStringArg logStringArg(hashFileName.toChar());
        this->m_bufferLogger.log_WARNING_HI_BL_LogFileValidationError(logStringArg, status);
    }
}

bool BufferLogger::File ::flush() {
    return true;
    // NOTE(if your fprime uses buffered file I/O, re-enable this)
    /*bool status = true;
    if(this->mode == File::Mode::OPEN)
    {
      const Os::File::Status fileStatus = this->osFile.flush();
      if(fileStatus == Os::File::OP_OK)
      {
        status = true;
      }
      else
      {
        status = false;
      }
    }
    return status;*/
}

void BufferLogger::File ::close() {
    if (this->m_mode == File::Mode::OPEN) {
        // Close file
        this->m_osFile.close();
        // Write out the hash file to disk
        this->writeHashFile();
        // Update mode
        this->m_mode = File::Mode::CLOSED;
    }
}

}  // namespace Svc
